#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Created on 2013-9-5

@author: iori
'''
import os, threading
import json
import logging, unittest
import re
import time
from ConfigParser import ConfigParser
import requests

from utils import RangeFile, md5_file, md5_str, crc32_file, pcs_path_safe, get_session
from baidu import Baidu
from const import CONFIG_FILE


logging.basicConfig(level=logging.INFO)
logger_requests = logging.getLogger('requests')
logger_requests.setLevel(logging.WARN)
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)

class PCS(object):
    def __init__(self):
        '''  注意 path参数必须以 /apps开头，然后是你开通 pcs api权限时指定的目录，
        当作一个linux系统来看的话， /apps相当/home目录,
        你开通 pcs api权限，相当创建一个新用户
        '''
        self.sn = get_session()
        with open(CONFIG_FILE, 'rb') as fp:
            self.config = json.load(fp)
        if not self.config.get('access_token'):
            self.refresh_access_token()
        self.params = {'access_token': self.config['access_token'],
                 'path':self.config['root_path']}  #所有的目录都在wust下进行操作，
        res = self.quota
        logger.info(res)
        error_code = res.get('error_code')
        if error_code == 111:
            self.refresh_access_token()

    def refresh_access_token(self):
        baidu = Baidu(self.config['username'], self.config['password'], self.sn)
        url = 'https://openapi.baidu.com/oauth/2.0/authorize'
        params = {'response_type': 'token',
                  'redirect_uri': 'oob',
                  'client_id': self.config['client_id'],
                  'scope': 'netdisk'}
        rsp = self.sn.get(url=url, params=params)
        logger.debug(rsp.url)
        access_token = re.findall(r'access_token=([^&]+)', rsp.url)
        if access_token:
            self.config['access_token'] = access_token[0]
            # save config
            with open(CONFIG_FILE, 'wb') as fp:
                json.dump(self.config, fp, indent=4)

    def execute(self, op, req='GET', **kw):
        url = 'https://pcs.baidu.com/rest/2.0/pcs/%s'%op
        params = self.params.copy()
        params.update(**kw)
        if 'from_path' in params:
            params['from'] = params['from_path']
            del params['from_path']
        if req.upper() == 'GET':
            if params['method'] == 'download':
                return self.get(url=url, params=params)  # response object
            return self.sn.get(url=url, params=params).json()
        elif req.upper() == 'POST':
            if kw['method'] == 'upload':
                if 'lpath' in params:
                    files = {'file': open(params['lpath'], 'rb')}
                    del params['lpath']
                elif 'fileobj' in params:
                    files = {'files': params['fileobj']}
                    del params['fileobj']
                else:
                    raise
                if 'type' in params and params['type']=='tmpfile': # chunk upload
                    del params['path']      # path is not needed
                return self.sn.post(url=url, files=files, params=params).json()
            data = params['data']
            del params['data']
            return self.sn.post(url=url, params=params, data=data).json()
    @property
    def quota(self, **kw):
        return self.execute(op='quota',method='info', **kw)


    def upload(self, lpath='', fileobj='', rpath='', ondup='overwrite', **kw):
        '''
        lpath 本地文件路径
        fileobj 文件对象，此时 rpath必须要
        rpath 远端文件路径， 包含最终文件名， 为空的话，用原文件名
        ondup     overwrite：表示覆盖同名文件；
                newcopy：表示生成文件副本并进行重命名，命名规则为“文件名_日期.后缀”。
        '''
        if lpath and os.path.isfile(lpath):
            if not rpath:
                rpath = os.path.split(lpath)[1]
            rpath = self.build_path(rpath)
            return self.execute(op='file', req='POST', method='upload', lpath=lpath, path=rpath, **kw)
        elif fileobj:
            if 'type' not in kw:  # 不是上传临时文件
                assert rpath
                rpath = self.build_path(rpath)
            return self.execute(op='file', req='POST', method='upload', fileobj=fileobj, path=rpath, **kw)

    def createsuperfile(self, flist, path, ondup='overwrite', **kw):
        data = {'block_list': [f['md5'] for f in flist]}
        data = json.dumps(data, separators=(',',':'))
        data = {'param':data}
        return self.execute(op='file', req='POST', method="createsuperfile", path=path, data=data, ondup=ondup, **kw)


    def upload_large(self, lpath, rpath='', limit_size=2000, ondup='overwrite', **kw):

        parts = 3
        res = [i for i in range(parts)]
        def upload_tmp(i, start, length):
            rf = RangeFile(lpath, start, length)
            info = self.upload(fileobj=rf, type='tmpfile')
            res[i] = info
        ts = []
        fsize = os.path.getsize(lpath)
        chunk_size = fsize/parts
        for i in range(parts):
            start = i* chunk_size
            length = chunk_size
            if i == parts-1:
                length =fsize - start
            t = threading.Thread(target=upload_tmp, args=(i, start, length))
            t.start()
            ts.append(t)
        for t in ts:
            t.join()
        fname = os.path.split(lpath)[1]
        return self.createsuperfile(res, fname, ondup, **kw)
        pass


    def download(self, rpath, lpath=''):
        ''' 下载文件
        rpath 云端路径， 最好相对路径
        lpath 本地存放位置
        '''
        res = self.execute(op='file', req='GET', method='download', path=rpath)
        name = os.path.split(rpath)[1]
        if not lpath:
            lpath = name
        elif os.path.isdir(lpath):
            lpath = os.path.join(lpath, name)
        chunk_size = 32*1024 # 32k per time
        with open(lpath, 'wb') as fp:
            while True:
                buff = res.read(chunk_size)
                if not buff: break
                fp.write(buff)
        print '%s download finished'

    def mkdir(self, path):
        return self.execute(op='file', req='GET',method='mkdir', path=path)
    def meta(self, path):
        return self.execute(op='file', req='GET',method='meta', path=path)
    def batch_meta(self, plist):
        pass
    def ls(self, path='', **kw):
        ''' 返回数据格式
            {
            "list":[{"fs_id":3528850315,
            "path":"/apps/yunform/music/hello",
            "ctime":1331184269,
            "mtime":1331184269,
            "block_list":["59ca0efa9f5633cb0371bbc0355478d8"],
            "size":13,
            "isdir":0
            }],
           "request_id":4043312670
            }
        '''
        return self.execute(op='file', method='list', path=path, **kw)
    def move(self, src, dest):
        return self.execute(op='file', method='move', from_path=src, to=dest)
    def batch_move(self, srcs, dests):
        pass
    def copy(self, src, dest):
        return self.execute(op='file', method='copy', from_path=src, to=dest)
    def batch_copy(self, srcs, dests):
        pass
    def delete(self, path):
        return self.execute(op='file', method='delete', path=path)
    def batch_delete(self, srcs, dests):
        pass
    def search(self, wd, path='', rec='0'):
        '''
        path dir to search
        wd file name key word
        rec recurisive search o no 1 yes
        '''
        return self.execute(op='search', wd=wd, path=path, rec=rec)
    ### the following are high function

    def rapidupload(self, lpath, rpath, ondup='overwrite'):

        kw = {'method': 'rapidupload',
              'path': rpath,
              'content-length': os.path.getsize(lpath),
              'content-md5': md5_file(lpath),
              'slice-md5': md5_str(open(lpath).read(256*1024)),
              'content-crc32': crc32_file(lpath),
              'ondup': ondup
              }
        print kw
        return self.execute(op='file', **kw)

    def build_path(self, path):
        ''' 反回一个PCS能识别的路径 '''
        if path.startswith(self.config['root_path']):
            return path
        if path.startswith('/'): path = path[1:] # 去掉最前面的/, 总是相对路径
        return '{0}/{1}'.format(self.config['root_path'], path)

    def exists(self, path):
        ''' 查看BCS是否存在指定的文件或路径'''
        path = self.build_path(path) # 变成合格的BCS路径
        prefix, name = os.path.split(path)
        parent = self.ls(prefix)
        if 'list' in parent :
            for f in parent['list']:
                if path == f['path']:
                    return True
        return False
    ### 下面是离线相关操作

    def cloud_dl(self, data):
        url = 'https://pcs.baidu.com/rest/2.0/pcs/services/cloud_dl'
        data.update(self.params)
        return self.sn.post(url, data).json()

    def add_task(self, url, path=''):
        ''' 功能说明， 离线下载文件 。
        @param url  要离线的url
        @param path 存放到PCS中的路径， 应当是相对路径，如： file1.py,  path/name.exe
                    任务ID号成功：
                    {"task_id":432432432432432,"request_id":3372220525}
                    任务并发太大：
                    {"error_code":36013,"error_msg":"too many tasks","request_id":3372220539}
        '''
        if not path:
            path = url[url.rfind('/'):]
        if self.exists(path):  # 不下载重复的文件
            logging.info('file %s exists', path)
            return
        path = pcs_path_safe(path)
        logger.info('cloud dl: %s to %s', url, path)
        while True:
            data = {'method': 'add_task',
                    'save_path': self.build_path(path),
                    'source_url': url}
            res = self.cloud_dl(data)
            if 'task_id' in res:
                return res['task_id']
            else:
                if res.get('error_code') == 36013: # too many task, wait a while then add try add agian
                    logger.info('two many tasks, wait for 5 second.')
                    time.sleep(5)
                else:
                    logger.info('add task %s: %s', url, res)
                    return


    def query_task(self, task_id):
        data = {'method': 'query_task',
                'task_ids': task_id}
        return self.cloud_dl(data)

    def list_task(self):
        data = {'method': 'list_task'}
        return self.cloud_dl(data)

    def cancel_task(self, task_id):
        data = {'method': 'cancel_task',
                'task_id': task_id}
        return self.cloud_dl(data)

    def save(self, data, path):
        ''' path应当是一合法的路径名，不能是目录'''
        if self.exists(path): return
        if data.startswith('http'):
            self.add_task(data, path)
        elif os.path.isfile(data):
            self.upload(lpath=data, rpath=path)
        else:
            self.upload(fileobj=data, rpath=path)

def get_pcs():
    return PCS()

def pcs_backup(data, path):
    pcs = get_pcs()
    if data.startswith('http'):
        pcs.add_task(data, path)
    elif os.path.isfile(data):
        pcs.upload(lpath=data, rpath=path)
    else:
        pcs.upload(fileobj=data, rpath=path)


class TestPCS(unittest.TestCase):
    def setUp(self):
        self.bcs = PCS()
    def test_dump(self):
        print 'hello, world'

    @unittest.skip('ok')
    def test_add_task(self):
        url = 'http://down.51voa.com/201311/se-ws-mouth-expressions-bad-taste-mouthpiece-02nov13.mp3'
        path = '51voa/test.mp3'
        print self.bcs.quota
        print self.bcs.build_path(path)
        tid = self.bcs.add_task(url, path)
        print tid
        print self.bcs.query_task(tid)
    @unittest.skip('ok')
    def test_save(self):
        content = 'test content'
        self.bcs.upload(fileobj=content, rpath='test.txt')
        self.bcs.save('../res/2013/12/afghan-interpreter-finally-lands-in-us.mp3', 'voa51/2013/12')


    @unittest.skip('ok')
    def test_exists(self):
        path = '51voa/test.mp3'
        assert self.bcs.exists(path)
        assert self.bcs.exists('waht')

if __name__ == '__main__':
    unittest.main()
